package com.yalantis.ucrop.view;

import com.yalantis.ucrop.callback.OverlayViewChangeListener;
import com.yalantis.ucrop.util.Config;
import com.yalantis.ucrop.util.LogUtils;
import com.yalantis.ucrop.util.NumCalcUtil;
import com.yalantis.ucrop.util.RectUtils;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

import static ohos.agp.render.Canvas.ClipOp.DIFFERENCE;
import static ohos.agp.render.Canvas.PorterDuffMode.CLEAR;

/**
 * Created by Oleksii Shliama (https://github.com/shliama).
 * <p/>
 * This view is used for drawing the overlay on top of the image. It may have frame, crop guidelines and dimmed area.
 * This must have LAYER_TYPE_SOFTWARE to draw itself properly.
 */
public class OverlayView extends Component implements Component.DrawTask, Component.TouchEventListener, Component.LayoutRefreshedListener {

    public static final int FREESTYLE_CROP_MODE_DISABLE = 0;
    public static final int FREESTYLE_CROP_MODE_ENABLE = 1;

    public static final int DEFAULT_FREESTYLE_CROP_MODE = FREESTYLE_CROP_MODE_DISABLE;

    private final RectFloat mCropViewRect = new RectFloat();
    private final RectFloat mTempRect = new RectFloat();

    protected int mThisWidth, mThisHeight;
    protected float[] mCropGridCorners;
    protected float[] mCropGridCenter;

    private int mCropGridRowCount, mCropGridColumnCount;
    private float mTargetAspectRatio;
    private float[] mGridPoints = null;
    private boolean mShowCropFrame, mShowCropGrid;
    private boolean mCircleDimmedLayer;
    private int mDimmedColor;
    private final Path mCircularPath = new Path();
    private final Paint mDimmedStrokePaint = new Paint();
    private final Paint mCropGridPaint = new Paint();
    private final Paint mCropFramePaint = new Paint();
    private final Paint mCropFrameCornersPaint = new Paint();
    private int mFreestyleCropMode = DEFAULT_FREESTYLE_CROP_MODE;
    private float mPreviousTouchX = -1, mPreviousTouchY = -1;
    private int mCurrentTouchCornerIndex = -1;
    private final int mTouchPointThreshold;
    private final int mCropRectMinSize;
    private final int mCropRectCornerTouchAreaLineLength;

    private OverlayViewChangeListener mCallback;

    private boolean mShouldSetupCropBounds;

    {
        mTouchPointThreshold = 30;
        mCropRectMinSize = 100;
        mCropRectCornerTouchAreaLineLength = 10;
    }

    public OverlayView(Context context, AttrSet attrs) {
        super(context, attrs, "0");
        init();
    }

    public void setOverlayViewChangeListener(OverlayViewChangeListener callback) {
        mCallback = callback;
    }


    public RectFloat getCropViewRect() {
        return mCropViewRect;
    }

    @Deprecated
    public boolean isFreestyleCropEnabled() {
        return mFreestyleCropMode == FREESTYLE_CROP_MODE_ENABLE;
    }

    @Deprecated
    public void setFreestyleCropEnabled(boolean freestyleCropEnabled) {
        mFreestyleCropMode = freestyleCropEnabled ? FREESTYLE_CROP_MODE_ENABLE : FREESTYLE_CROP_MODE_DISABLE;
    }

    /**
     * 设置剪切
     *
     * @param cropGridRowCount cropGridRowCount
     */
    public void setCropGridRowCount( int cropGridRowCount) {
        mCropGridRowCount = cropGridRowCount;
        mGridPoints = null;
    }

    /**
     * Setter for {@link #mShowCropFrame} variable.
     *
     * @param showCropFrame - set to true if you want to see a crop frame rectangle on top of an image
     */
    public void setShowCropFrame(boolean showCropFrame) {
        mShowCropFrame = showCropFrame;
    }

    /**
     * Setter for {@link #mShowCropGrid} variable.
     *
     * @param showCropGrid - set to true if you want to see a crop grid on top of an image
     */
    public void setShowCropGrid(boolean showCropGrid) {
        mShowCropGrid = showCropGrid;
    }

    /**
     * Setter for {@link #mDimmedColor} variable.
     *
     * @param dimmedColor - desired color of dimmed area around the crop bounds
     */
    public void setDimmedColor( int dimmedColor) {
        mDimmedColor = dimmedColor;
    }

    /**
     * This method sets aspect ratio for crop bounds.
     *
     * @param targetAspectRatio - aspect ratio for image crop (e.g. 1.77(7) for 16:9)
     */
    public void setTargetAspectRatio(final float targetAspectRatio) {
        mTargetAspectRatio = targetAspectRatio;
        LogUtils.LogInfo("qqqqqqqq:","mTargetAspectRatio:"+mTargetAspectRatio);
        LogUtils.LogInfo("qqqqqqqq:","mThisWidth:"+mThisWidth);
        if (mThisWidth > 0) {
            setupCropBounds();
            EventHandler eventHandler = new EventHandler( EventRunner.getMainEventRunner());
            eventHandler.postTask(OverlayView.super::invalidate);//风险等级2
        } else {
            mShouldSetupCropBounds = true;
        }
    }

    /**
     * This method setups crop bounds rectangles for given aspect ratio and view size.
     * {@link #mCropViewRect} is used to draw crop bounds - uses padding.
     */
    public void setupCropBounds() {
        int height = (int) (mThisWidth / mTargetAspectRatio);
        if (height > mThisHeight) {
            LogUtils.LogInfo("qqqqqqqq1:","Height > mThisHeight:"+"true");
            int width = (int) (mThisHeight * mTargetAspectRatio);
            int halfDiff = (mThisWidth - width) / 2;
            mCropViewRect.modify(getPaddingLeft() + halfDiff, getPaddingTop(),
                    getPaddingLeft() + width + halfDiff, getPaddingTop() + mThisHeight);
        } else {
            LogUtils.LogInfo("qqqqqqqq1:","Height > mThisHeight:"+"false");
            int halfDiff = (mThisHeight - height) / 2;
            mCropViewRect.modify(getPaddingLeft(), getPaddingTop() + halfDiff,
                    getPaddingLeft() + mThisWidth, getPaddingTop() + height + halfDiff);
        }

        if (mCallback != null) {
            mCallback.onCropRectUpdated(mCropViewRect);
        }

        updateGridPoints();
    }

    private void updateGridPoints() {
        mCropGridCorners = RectUtils.getCornersFromRect(mCropViewRect);
        mCropGridCenter = RectUtils.getCenterFromRect(mCropViewRect);

        mGridPoints = null;
        mCircularPath.reset();
        mCircularPath.addCircle(mCropViewRect.getCenter().position[0], mCropViewRect.getCenter().position[1],
                Math.min(mCropViewRect.getWidth(), mCropViewRect.getHeight()) / 2.f, Path.Direction.CLOCK_WISE);
    }

    protected void init() {
//        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
//            setLayerType(LAYER_TYPE_SOFTWARE, null);
//        } 风险等级1
        addDrawTask((component, canvas) -> {
            drawDimmedLayer(canvas);
            drawCropGrid(canvas);
            LogUtils.LogInfo("qqqqqqq:","draw OverLayView");
        });
        setLayoutRefreshedListener(component -> {
            component.setLeft(getPaddingLeft());  //风险等级3
            component.setTop(getPaddingTop()); //风险等级3
            component.setRight(getWidth() - getPaddingRight()); //风险等级3
            component.setBottom(getHeight() - getPaddingBottom()); //风险等级3
            LogUtils.LogInfo("qqqqqqq:", getPaddingLeft()+"/"+getPaddingTop()+"/"+component.getRight()+"/"+component.getBottom());

            mThisWidth = component.getRight()-component.getLeft(); //风险等级3
            mThisHeight = component.getBottom()-component.getTop(); //风险等级3

            if (mShouldSetupCropBounds) {
                mShouldSetupCropBounds = false;
                setTargetAspectRatio(mTargetAspectRatio);
            }
        });
    }
    @Override
    public void onRefreshed(Component component) {

                component.setLeft(getPaddingLeft());  //风险等级3
                component.setTop(getPaddingTop()); //风险等级3
                component.setRight(getWidth() - getPaddingRight()); //风险等级3
                component.setBottom(getHeight() - getPaddingBottom()); //风险等级3

                mThisWidth = component.getRight()-component.getLeft(); //风险等级3
                mThisHeight = component.getBottom()-component.getTop(); //风险等级3

                if (mShouldSetupCropBounds) {
                    mShouldSetupCropBounds = false;
                    setTargetAspectRatio(mTargetAspectRatio);
                }
            }
    /**
     * 更新剪切view
     *
     * @param touchX touchX
     * @param touchY touchY
     */
    private void updateCropViewRect(float touchX, float touchY) {
        mTempRect.modify(mCropViewRect);

        switch (mCurrentTouchCornerIndex) {
            // resize rectangle
            case 0:
                mTempRect.modify(touchX, touchY, mCropViewRect.right, mCropViewRect.bottom);
                break;
            case 1:
                mTempRect.modify(mCropViewRect.left, touchY, touchX, mCropViewRect.bottom);
                break;
            case 2:
                mTempRect.modify(mCropViewRect.left, mCropViewRect.top, touchX, touchY);
                break;
            case 3:
                mTempRect.modify(touchX, mCropViewRect.top, mCropViewRect.right, touchY);
                break;
            // move rectangle
            case 4:
                mTempRect.translate(NumCalcUtil.subtract(touchX , mPreviousTouchX)
                        , NumCalcUtil.subtract(touchY , mPreviousTouchY));
                if (mTempRect.left > getLeft() && mTempRect.top > getTop()
                        && mTempRect.right < getRight() && mTempRect.bottom < getBottom()) {
                    mCropViewRect.modify(mTempRect);
                    updateGridPoints();
                    EventHandler eventHandler = new EventHandler( EventRunner.getMainEventRunner());
                    eventHandler.postTask(OverlayView.super::invalidate);//风险等级2
                }
                return;
        }

        boolean changeHeight = mTempRect.getHeight() >= mCropRectMinSize;
        boolean changeWidth = mTempRect.getWidth() >= mCropRectMinSize;
        mCropViewRect.modify(
                changeWidth ? mTempRect.left : mCropViewRect.left,
                changeHeight ? mTempRect.top : mCropViewRect.top,
                changeWidth ? mTempRect.right : mCropViewRect.right,
                changeHeight ? mTempRect.bottom : mCropViewRect.bottom);

        if (changeHeight || changeWidth) {
            updateGridPoints();
            EventHandler eventHandler = new EventHandler( EventRunner.getMainEventRunner());
            eventHandler.postTask(OverlayView.super::invalidate);//风险等级2
        }
    }

    /**
     * getCurrentTouchIndex
     *
     * @param touchX touchX
     * @param touchY touchY
     * @return CurrentTouchIndex
     * @noinspection checkstyle:JavadocMethod
     */
    private int getCurrentTouchIndex(float touchX, float touchY) {
        int result = 4;
        int closestPointIndex = -1;
        double closestPointDistance = mTouchPointThreshold;
        for (int i = 0; i < 8; i += 2) {
            double distanceToCorner = Math.sqrt(Math.pow(NumCalcUtil.subtract(touchX , mCropGridCorners[i]), 2)
                    + Math.pow(NumCalcUtil.subtract(touchY , mCropGridCorners[i + 1]), 2));
            if (distanceToCorner < closestPointDistance) {
                closestPointDistance = distanceToCorner;
                closestPointIndex = i / 2;
            }
        }

        if (mFreestyleCropMode != FREESTYLE_CROP_MODE_ENABLE || closestPointIndex >= 0 || !mCropViewRect.isInclude(touchX, touchY)) {//        for (int i = 0; i <= 8; i += 2) {
//
//            double distanceToCorner;
//            if (i < 8) { // corners
//                distanceToCorner = Math.sqrt(Math.pow(touchX - mCropGridCorners[i], 2)
//                        + Math.pow(touchY - mCropGridCorners[i + 1], 2));
//            } else { // center
//                distanceToCorner = Math.sqrt(Math.pow(touchX - mCropGridCenter[0], 2)
//                        + Math.pow(touchY - mCropGridCenter[1], 2));
//            }
//            if (distanceToCorner < closestPointDistance) {
//                closestPointDistance = distanceToCorner;
//                closestPointIndex = i / 2;
//            }
//        }
            result = closestPointIndex;
        }

        return result;
    }

    /**
     * This method draws dimmed area around the crop bounds.
     *
     * @param canvas - valid canvas object
     */
    protected void drawDimmedLayer( Canvas canvas) {
        canvas.save();
        if (mCircleDimmedLayer) {
            canvas.clipPath(mCircularPath, DIFFERENCE);
        } else {
            canvas.clipRect(mCropViewRect,DIFFERENCE);
        }
        canvas.drawColor(mDimmedColor,CLEAR);//无法设置透明度，导致全黑
        canvas.restore();

        if (mCircleDimmedLayer) { // Draw 1px stroke to fix antialias
            canvas.drawCircle(NumCalcUtil.add(mCropViewRect.left,mCropViewRect.getWidth()*2)
                    , NumCalcUtil.add(mCropViewRect.right,mCropViewRect.getHeight()*2),
                    Math.min(mCropViewRect.getWidth(), mCropViewRect.getHeight()) / 2.f, mDimmedStrokePaint);
        }
    }

    /**
     * This method draws crop bounds (empty rectangle)
     * and crop guidelines (vertical and horizontal lines inside the crop bounds) if needed.
     *
     * @param canvas - valid canvas object
     */
    protected void drawCropGrid( Canvas canvas) {
        if (mShowCropGrid) {
            if (mGridPoints == null && !mCropViewRect.isEmpty()) {

                mGridPoints = new float[(mCropGridRowCount) * 4 + (mCropGridColumnCount) * 4];
                int index = 0;
                for (int i = 0; i < mCropGridRowCount; i++) {
                    mGridPoints[index++] = mCropViewRect.left;
                    mGridPoints[index++] = (mCropViewRect.getHeight() * (((float) i + 1.0f) / (float) (mCropGridRowCount + 1))) + mCropViewRect.top;
                    mGridPoints[index++] = mCropViewRect.right;
                    mGridPoints[index++] = (mCropViewRect.getHeight() * (((float) i + 1.0f) / (float) (mCropGridRowCount + 1))) + mCropViewRect.top;
                }

                for (int i = 0; i < mCropGridColumnCount; i++) {
                    mGridPoints[index++] = (mCropViewRect.getWidth() * (((float) i + 1.0f) / (float) (mCropGridColumnCount + 1))) + mCropViewRect.left;
                    mGridPoints[index++] = mCropViewRect.top;
                    mGridPoints[index++] = (mCropViewRect.getWidth() * (((float) i + 1.0f) / (float) (mCropGridColumnCount + 1))) + mCropViewRect.left;
                    mGridPoints[index++] = mCropViewRect.bottom;
                }
                canvas.drawLines(mGridPoints, mCropGridPaint);
            }
        }

        if (mShowCropFrame) {
            canvas.drawRect(mCropViewRect, mCropFramePaint);
        }

        if (mFreestyleCropMode != FREESTYLE_CROP_MODE_DISABLE) {
            canvas.save();

            mTempRect.modify(mCropViewRect);
            mTempRect.fuse(mCropRectCornerTouchAreaLineLength, -mCropRectCornerTouchAreaLineLength);//风险预警2
            canvas.clipRect(mTempRect, DIFFERENCE);

            mTempRect.modify(mCropViewRect);
            mTempRect.fuse(-mCropRectCornerTouchAreaLineLength, mCropRectCornerTouchAreaLineLength);
            canvas.clipRect(mTempRect, DIFFERENCE);

            canvas.drawRect(mCropViewRect, mCropFrameCornersPaint);

            canvas.restore();
        }
    }

    /**
     * processStyledAttributes
     *
     * @param a a
     */
    protected void processStyledAttributes( Config a) {
        mCircleDimmedLayer = a.getmCircleDimmedLayer();
        mDimmedColor = a.getmDimmedColor();
        mDimmedStrokePaint.setColor(new Color(mDimmedColor));
        mDimmedStrokePaint.setStyle(Paint.Style.STROKE_STYLE);
        mDimmedStrokePaint.setStrokeWidth(1);

        initCropFrameStyle(a);
        mShowCropFrame = a.getmShowCropFrame();

        initCropGridStyle(a);
        mShowCropGrid = a.getmShowCropGrid();
    }

    /**
     * initCropFrameStyle
     *
     * @param a a
     */
    private void initCropFrameStyle(Config a) {
        int cropFrameStrokeSize = a.getCropFrameStrokeSize();
        int cropFrameColor = a.getCropFrameColor();
        mCropFramePaint.setStrokeWidth(cropFrameStrokeSize);
        mCropFramePaint.setColor(new Color(cropFrameColor));
        mCropFramePaint.setStyle(Paint.Style.STROKE_STYLE);

        mCropFrameCornersPaint.setStrokeWidth(cropFrameStrokeSize * 3);
        mCropFrameCornersPaint.setColor(new Color(cropFrameColor));
        mCropFrameCornersPaint.setStyle(Paint.Style.STROKE_STYLE);
    }

    /**
     * initCropGridStyle
     *
     * @param a a
     */
    private void initCropGridStyle( Config a) {
        int cropGridStrokeSize = a.getCropGridStrokeSize();
        int cropGridColor = a.getCropGridColor();
        mCropGridPaint.setStrokeWidth(cropGridStrokeSize);
        mCropGridPaint.setColor(new Color(cropGridColor));

        mCropGridRowCount = a.getmCropGridRowCount();
        mCropGridColumnCount = a.getmCropGridColumnCount();
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        drawDimmedLayer(canvas);
        drawCropGrid(canvas);
        LogUtils.LogInfo("qqqqqqq:","draw OverLayView");
    }
//    @Override
//    protected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);
//        drawDimmedLayer(canvas);
//        drawCropGrid(canvas);
//    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (mCropViewRect.isEmpty() || mFreestyleCropMode == FREESTYLE_CROP_MODE_DISABLE) {
            return false;
        }

        float x = event.getPointerPosition(0).getX();
        float y = event.getPointerPosition(0).getY();

        if (event.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
            mCurrentTouchCornerIndex = getCurrentTouchIndex(x, y);
            boolean shouldHandle = mCurrentTouchCornerIndex != -1;
            if (!shouldHandle) {
                mPreviousTouchX = -1;
                mPreviousTouchY = -1;
            } else if (mPreviousTouchX < 0) {
                mPreviousTouchX = x;
                mPreviousTouchY = y;
            }
            return shouldHandle;
        }

        if (event.getAction()  == TouchEvent.POINT_MOVE) {
            if (event.getPointerCount() == 1 && mCurrentTouchCornerIndex != -1) {

                x = Math.min(Math.max(x, getPaddingLeft()), getWidth() - getPaddingRight());
                y = Math.min(Math.max(y, getPaddingTop()), getHeight() - getPaddingBottom());

                updateCropViewRect(x, y);

                mPreviousTouchX = x;
                mPreviousTouchY = y;

                return true;
            }
        }

        if (event.getAction()  == TouchEvent.PRIMARY_POINT_UP) {
            mPreviousTouchX = -1;
            mPreviousTouchY = -1;
            mCurrentTouchCornerIndex = -1;

            if (mCallback != null) {
                mCallback.onCropRectUpdated(mCropViewRect);
            }
        }

        return false;
    }
}
